home Today's News Magazine Archives Vendor Guide 2001 Search isdmag.com

Editorial
Today's News
News Archives
On-line Articles
Current Issue
Magazine Archives
Subscribe to ISD


Directories:
Vendor Guide 2001
Advertiser Index
Event Calendar


Resources:
Resources and Seminars
Special Sections


Information:
2001 Media Kit
About isdmag.com
Writers Wanted!
Search isdmag.com
Contact Us









The Design and Operation of a Multithread VHDL Testbench

The use of multithread architecture along with other testing concepts such as self-checking and behavioral level modeling can provide a high level of testing sophistication and flexibility.

By Mike Francois


Upcoming verification tools, such as Vera and Specman, use high-level object-oriented hardware verification languages (HVL) to simulate Verilog and VHDL designs. They allow for the development of flexible, reusable tests and will no doubt, improve the verification process for many companies in many situations.

However, as with most EDA tools, they are not a one-size-fits-all solution. There are financial costs (licenses) and schedule costs (learning curve) that must be absorbed to make these tools part of a company's design and verification process.

Given these facts, the current capabilities of VHDL, and the promise of new object-oriented language extensions, there will continue to be justification for the creation of VHDL testbenchs to get the simulation job done.

The testbench architecture described here was used to verify the Flight Management Peripheral I/O (FMPIO) ASIC designed by Smiths Industries Aerospace. This ASIC provides multiple support functions for a Motorola MC68040 processor, including multiple DMA, UART, serial ARINC channels, dynamic bus sizing, timers, and error detection/correction (EDC). The ASIC contains 120,000 random gates, 9 memories, and an embedded microprocessor. This ASIC was completed with first pass silicon.

Multithread testbench defined Multithread testbench is defined as a VHDL testbench that allows the operation of multiple concurrent processes, each running a separate simulation thread while gracefully sharing the resource of the VHDL design under test.

Although this definition provides a concise academic definition of a multithread testbench, an understanding of its two basic tenets, multiple concurrent processes and resource sharing, require additional explanation.

The concept of multiple concurrent processes is well known to VHDL designers. It is one of the unique language features that allow VHDL to model the parallel nature of circuit operation. Extending this idea to a VHDL testbench is not difficult, but is best illustrated through the use of a simple example.


Figure 1 Two-process VHDL device

Figure 1 represents a VHDL device that contains two concurrent processes. Each of these processes is a self-contained block of VHDL code that performs a transformation function between the input and output signals. The output of each process is dependent only on its corresponding input and is unaffected by any other stimulus in the design.


Figure 2 Two-process VHDL device with testbench

To validate the correct operation of a VHDL device various stimuli must be applied to the inputs while the device outputs are monitored. As shown in Figure 2 the generation of input stimuli is the responsibility of the VHDL testbench.

This testbench contains a series of VHDL assignment and delay statements within a single process or simulation thread. These statements are performed sequentially starting at the top of the process and generate both timing and functional stimuli to the VHDL device inputs.

This single-thread testbench is basic and easy to code. But because it drives both input signals from a single block of code, it requires careful arrangement of assignment and delay statements to generate the input stimulus. This style of testbench although simple does not normally lend itself to the testing of complex VHDL devices.


Figure 3 Two-process VHDL device with two-process testbench

Replacing the testbench with one that supports multiple concurrent processes is presented in Figure 3. The assignment and delay statements are now divided between two VHDL code blocks (or processes) and constructed in such a way that the same input timing and functional stimulus are provided. The complexity and shortcomings imposed by the single-thread, sequential code are overcome and the operation of each signal is more obvious and straightforward. This in turn enhances maintainability, reuse, and flexibility of the testbench. As the complexity of the VHDL device increases, the benefit of a separate, concurrent process becomes more obvious.

During the testing of the FMPIO ASIC, as many as 30 individual concurrent testbench processes were operating at the same time. Each of these processes handled some specific function of the overall simulation effort, such as a serial channel or DMA operation. All of the processes were written in a modular, self-contained fashion so that they could easily be mixed and matched depending on the testing requirements. This mixing allowed for over 70 unique test driver combinations to be created for the verification of the FMPIO ASIC.


Figure 4 FMPIO ASIC concurrent process block diagram

The concept and implementation of multiple independent processes within a testbench is a relatively simple procedure. However it creates a new, and arguably more difficult problem, namely that of VHDL device resource sharing. This then is the other leg of multithread operation.

A VHDL shared resource can be as simple as an input discrete or as complex as a multibus system backplane. What a shared resource is and how the various processes use it depends on the application of the VHDL device under test. In general, VHDL shared resources tend to be signals at the interface of the design that need to be accessed by more than one testbench process. A shared resource common to many designs (as shown in Figure 4) is the processor or system interface bus.

The implementation of resource sharing within a testbench requires communication and arbitration between the various independent simulation processes. Support for these operations is a standard feature of object-oriented software languages and will hopefully be adopted as part of the VHDL language in the near future. However, by using the language as it exists today with creative testbench coding, support for shared VHDL resources can be provided.

Resource sharing of the system interface bus The primary resource that required sharing during the FMPIO ASIC testing was the system interface bus. This resource was comprised of a 32-bit address bus, a 64-bit data bus, and more than a dozen control signals. This resource was heavily used and shared among the 30 testbench processes to allow access to the internal registers within the ASIC.

To ensure test flexibility, no restrictions were placed on when or for what purpose the interface bus could be requested by any of the simulation processes. Since each process operated independently of all others, this flexibility led to multiple conflicting requests for the bus during each simulation. The problem of resource sharing was solved by the application of a VHDL resolution function on a resolved signal.

Because a resolved signal is allowed to have multiple drive sources, it lent itself well to the problem of multiple requesting processes. A pair of resolved signals of type natural called Request and Grant were connected as shown in Figure 5, between the interface bus resource and each of the simulation processes. Signal Request was driven from multiple simulation processes to the bus resource, while Grant was driven out of the resource to the processes. Since these two signals have a unique purpose that needed to be duplicated for every resource in the design, they were formed into a VHDL record named a Resource Channel.

To drive the Request signal each simulation process was assigned a unique priority value by the test developer. The value was based on the relative importance of the simulation operation performed by each process (i.e. higher priority tasks were assigned higher priority request values). As each of these values was driven during bus resource requests; arbitration or signal resolution was performed to select the highest priority process needing service.


Figure 5 Process resource sharing

Once the interface bus resource receives the request value, it provides usage rights to the high priority process by assigning the request value onto the Grant signal. Each of the requesting processes has been held in a wait state watching for activity on the Grant signal. Once this occurs and the process verifies that the grant value matches its request, it proceeds with the remainder of the bus operation.

The code used to suspend each of the processes can be simple or complex depending on which features are needed. The rudiments of the code are formed from a VHDL while loop:

ResourceChannel.Request < = Priority; --Request resource.
wait for 0 ns;

while ResourceChannel.Grant /= Priority loop --Wait for resource grant.
wait on ResourceChannel.Grant;
end loop ;

Depending on the function of each simulation, it is possible that one or more requesting processes will be blocked for long periods of time. If this is not an acceptable side effect, a simple change to the above code will ensure that the longer a process has requested a resource, the higher priority it is assigned.

ResourceChannel.Request < = Priority; --Request resource.
wait for 0 ns;

while ResourceChannel.Grant /= Priority loop --Wait for resource grant.
wait on ResourceChannel.Grant;
Priority := Priority + 10; --Increase req pri value;
ResourceChannel.Request < = Priority;
end loop ;

Once a process has been granted access to the bus, data and address required for the bus operation are passed to the resource by way of shared variables defined in the VHDL testbench package. To ensure that the granted process has properly registered the data, the resource handler code must wait for a small but "real" amount of simulation time. All resource handlers within the FMPIO ASIC testbench waited for 1 ns.

The handler reads the shared variables and performs the bus operation. After completion, the resource removes the priority value from the Grant signal, informing all processes that the action has finished and that the resource arbitration cycle will begin again. The requesting process that has just received service now removes its request and continues on with its next simulation task.

A condition that should be carefully avoided is allowing two or more processes to use the same priority value. Doing this can result in a resource grant being awarded to multiple processes that will lead to indeterminate simulation results.

VHDL multithread code example VHDL code for implementing a shared resource and its operation is provided in the following example. Some portions of the code and comments have been condensed or removed.

VHDL Testbench Package The VHDL testbench package defines several of the required components needed to properly use a shared resource. The resource resolution function ( ResolvedPriority ) is defined and coded here as are procedure calls ( AsicWrite ) used by the test driver file. Additionally, any shared variables required for data passing need to be declared at this level of abstraction.

Package
package test_pkg is

-------------------

type NaturalArray is array( positive range < > ) of natural;

function ResolvedPriority( ArrayValues_v : NaturalArray ) return natural ;

-------------------------------

--Define the Resource Channel that combines both the Request
--and Grant signal into a single record to allow less complex
--port definitions.

--------------------------------

type ResourceChannel is record
Request : ResolvedPriority natural;
Grant : ResolvedPriority natural;
end record ResourceChannel;

---------------------------------

--AsicWrite is called by the simulation processes to gain
--access to the bus resource. This procedure is NOT the
--resource handler, but the caller TO the handler.
----------------------------------

procedure AsicWrite(
signal Resource_s : inout ResourceChannel;
Priority_c : in natural ;
Add_c : in std_logic_vector( 7 downto 0 );
Data_c : in std_logic_vector( 7 downto 0 ));

-----------------------------------

--Define the shared variables that will be needed to pass
--data between the AsicWrite procedure and the resource handler.
-----------------------------------

type ProcessorBusRecord is record
Add : std_logic_vector( 7 downto 0 );
Data : std_logic_vector( 7 downto 0 );
end record ProcessorBusRecord;

shared variable PBus_sv : ProcessorBusRecord;

end package test_pkg;

-----------------------------------

Package Body
package body test_pkg is
------------------

--Resolution function that selects the highest value natural
--out of an array of naturals. This array of naturals comes
--from the multi simulation processes driving their priority
--values onto the Request signal.
------------------------------------

function ResolvedPriority( ArrayValues_v : NaturalArray ) return natural is

------------------------------------

variable RetValue_v : natural := 0;

begin

for Index in ArrayValues_v'range loop

if ( ArrayValues_v( Index ) > RetValue_v ) then
RetValue_v := ArrayValues_v( Index );
end if ;

end loop ;

return RetValue_v;

end function ResolvedPriority;

------------------------------------

------------------------------------

--AsicWrite is called by the simulation processes to gain
--access to the bus resource. This procedure is NOT the
--resource handler, but the caller TO the handler.
-------------------------------------

procedure AsicWrite(
signal Resource_s : inout ResourceChannel;
Priority_c : in natural;
Add_c : in std_logic_vector( 7 downto 0 );
Data_c : in std_logic_vector( 7 downto 0 )) is
variable Priority_v : natural ;

begin

Priority_v := Priority_c;
Resource_s.Request < = Priority_v;
wait for 0 ns;

while Resource_s.Grant /= Priority_v loop
wait on Resource_s.Grant;
end loop ;

--------------------------------------

--Place bus cycle data into shared variables for
--access by the resource handler.
--------------------------------------

PBus_sv.Add := Add_c;
PBus_sv.Data := Data_c;

--Wait until bus cycle is over.
wait un til Resource_s.Grant = 0;

--Release the request for the bus.
Resource_s.Request < = 0;
wait for 0 ns;

end procedure AsicWrite;
end package body test_pkg;

VHDL Testbench
The testbench code contains the actual resource process ( ProcessorBusDriver ) that communicates with the VHDL design under test. There are, however, no restrictions on placing this code in a component within the testbench, or in any other file. The only requirement is that the test driver processes and the resource have visibility to the same "resource channel" record.

The timing contained in the ProcessorBusDriver process is arbitrary and has been provided only to facilitate this example.

Testbench Entity
entity TestBench isend entity TestBench;

Testbench Architecture
architecture TestBenchArch of TestBench is

--------------------------------

--Call out design under test

--------------------------------

component Asic
port ( Add_p : in std_logic_vector( 7 downto 0 );
Data_p : in std_logic_vector( 7 downto 0 ));
end component ;

-------------------------------

--Call out test driver file

-------------------------------

component TestDriver
port ( PBus_p : inout ResourceChannel );
end component ;

signal PBus_s : ResourceChannel;
signal Add_s : std_logic_vector( 7 downto 0 ) := "ZZZZZZZZ";
signal Data_s : std_logic_vector( 7 downto 0 ) := "ZZZZZZZZ";

begin

TB1: TestDriver port map ( PBus_p => PBus_s );

U1 : Asic port map (Add_p => Add_s, Data_p => Data_s );

ProcessorBusDriver : process is

--------------------------------

begin

--------------------------------

--Wait for a positive value on the Resource.Request
--signal before proceeding. A positive value
--indicates that a simulation process is requesting
--this resource. Only the highest value will appear
--on the Request signal due to the resolution
--function.

--------------------------------

if ( PBus_s.Request = 0 ) then
wait on PBus_s.Request;
end if ;

---------------------------------

--Acknowledge the Request signal with the Grant
--signal.

---------------------------------

PBus_s.Grant < = PBus_s.Request;

---------------------------------

--Wait a short amount of time for the grant to be
--registered with the simulation process and for
--the process to load the shared variable data.

---------------------------------

wait for 1 ns;

---------------------------------

--Get the data for the resource operation from the
--shared variable data record and perform the bus
--operation.

----------------------------------

Add_s < = PBus_sv.Add;
Data_s < = PBus_sv.Data;
wait for 5 ns;

Add_s < = "ZZZZZZZZ";
Data_s < = "ZZZZZZZZ";

----------------------------------

--Cycle has ended, release grant, wait and return
--to the beginning of this process to arbitrate
--again.

----------------------------------

PBus_s.Grant < = 0;
wait for 5 ns;

end process ProcessorBusDriver;

end TestBenchArch;

VHDL Test Driver
The VHDL test driver file is where the multiple independent simulation processes reside. This example contains three processes but there is no restriction on this number beyond the programmer's ability to maintain the code.

In this example each of the three processes is requesting the same resource through the AsicWrite procedure shown above in the VHDL testbench package. The name of the resource being called, the priority level, and any additional data needed to perform the operation are passed to the resource by the procedure call.

Test Driver Entity
entity TestDriver is
port ( PBus_p : inout ResourceChannel);
end entity TestDriver;

Test Driver Architecture
architecture TestName01 of TestDriver is

----------------------------

begin

----------------------------------

--Process A is assigned a request priority value of 1.

-----------------------------------

ProcessA : process is

---------------

begin

wait for 5 ns;

AsicWrite( PBus_p, 1, x"11", x"11" );

wait ; --Stall out process for example.

end process ProcessA;

-------------------------------

--Process B is assigned a request priority value of 2.

-------------------------------

ProcessB : process is

-------------------------------

begin

--------------

wait for 5 ns;

AsicWrite( PBus_p, 2, x"22", x"22" );

wait ; --Stall out process for example.

end process ProcessB;

--------------------------------

--Process C is assigned a request priority value of 3.

--------------------------------

ProcessC : process is

---------------

begin

wait for 8 ns;

AsicWrite( PBus_p, 3, x"33", x"33" );

wait ; --Stall out process for example.

end process ProcessC;

end TestName01;

Simulation Output
The following figure shows a simulation run of the example code above.

Process A with a priority of 1 and process B with a priority of 2 both request the bus resource at 5ns. Process B, having the higher priority is granted first access to the bus. Data is passed to the resource handler and a bus cycle begins. Process B drops its request at 15ns after the bus operation has been completed, but in the interim (at 8ns) process C having a priority of 3, has registered its request.

At 15ns when the second round of arbitration begins, process A and C are both waiting for the bus resource. C takes priority and performs its cycle first, followed by process A with its lower priority.


Figure 6 VHDL code example simulation

Development and operation of a multithread testbench is possible using the VHDL language as it exists today. This style testbench provides flexibility and reuse that make it a reasonable alternative to the use of hardware verification language tools that require additional funding and training.

Mike Francois is a graduate of Michigan State University and has worked in aerospace and defense for the past 10 years. He currently helps head VHDL ASIC development and verification at Smiths Industries Aerospace in Grand Rapids, Michigan.


Sponsor Links

All material on this site Copyright © 2001 CMP Media Inc. All rights reserved.